﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using EuroManager.Domain.Simulator.Helpers;

namespace EuroManager.Domain.Simulator.Engine
{
    public struct Vector2D
    {
        private static Vector2D zero = new Vector2D();
        private static Vector2D unitX = new Vector2D(1, 0);
        private static Vector2D unitY = new Vector2D(0, 1);

        private float x;
        private float y;

        public Vector2D(float x, float y)
        {
            Contract.Requires(x.IsRational());
            Contract.Requires(y.IsRational());

            this.x = x;
            this.y = y;
        }

        public static Vector2D Zero
        {
            get { return zero; }
        }

        public static Vector2D UnitX
        {
            get { return unitX; }
        }

        public static Vector2D UnitY
        {
            get { return unitY; }
        }

        public float X
        {
            get { return x; }
            set
            {
                Contract.Requires(value.IsRational());
                x = value;
            }
        }

        public float Y
        {
            get { return y; }
            set
            {
                Contract.Requires(value.IsRational());
                y = value;
            }
        }

        public bool IsZero
        {
            get { return x == 0 && y == 0; }
        }

        public float Length
        {
            get { return Maths.Sqrt(x * x + y * y); }
        }

        public bool IsNormalized
        {
            get { return Maths.Equals(Length, 1); }
        }

        public static float Dot(Vector2D vector1, Vector2D vector2)
        {
            return vector1.X * vector2.X + vector1.Y * vector2.Y;
        }

        public static float Cross(Vector2D vector1, Vector2D vector2)
        {
            return vector1.X * vector2.Y - vector1.Y * vector2.X;
        }

        public static bool operator ==(Vector2D vector1, Vector2D vector2)
        {
            return vector1.X == vector2.X && vector1.Y == vector2.Y;
        }

        public static bool operator !=(Vector2D vector1, Vector2D vector2)
        {
            return vector1.X != vector2.X || vector1.Y != vector2.Y;
        }

        public static Vector2D operator +(Vector2D vector1, Vector2D vector2)
        {
            return new Vector2D(vector1.X + vector2.X, vector1.Y + vector2.Y);
        }

        public static Vector2D operator -(Vector2D vector1, Vector2D vector2)
        {
            return new Vector2D(vector1.X - vector2.X, vector1.Y - vector2.Y);
        }

        public static Vector2D operator -(Vector2D vector)
        {
            return new Vector2D(-vector.X, -vector.Y);
        }

        public static Vector2D operator *(Vector2D vector, float factor)
        {
            Contract.Requires(factor.IsRational());

            return new Vector2D(vector.X * factor, vector.Y * factor);
        }

        public static Vector2D operator *(float factor, Vector2D vector)
        {
            return vector * factor;
        }

        public static Vector2D operator /(Vector2D vector, float factor)
        {
            Contract.Requires(factor != 0);
            Contract.Requires(factor.IsRational());

            return new Vector2D(vector.X / factor, vector.Y / factor);
        }

        public static bool Equals(Vector2D v1, Vector2D v2, float delta)
        {
            return Maths.Equals(v1.X, v2.X, delta) && Maths.Equals(v1.Y, v2.Y, delta);
        }

        public override bool Equals(object obj)
        {
            if (obj is Vector2D)
            {
                return this == (Vector2D)obj;
            }
            else
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return x.GetHashCode() + y.GetHashCode();
        }

        public override string ToString()
        {
            return "[" + x + ";" + y + "]";
        }

        public bool IsParallel(Vector2D other)
        {
            float delta = Maths.Max(1, x + y + other.x + other.y) * Maths.Epsilon;
            return Maths.Equals(Cross(this, other), 0, delta);
        }

        public bool IsParallelWithSense(Vector2D other)
        {
            return IsParallel(other) && Maths.Sign(x) == Maths.Sign(other.x) && Maths.Sign(y) == Maths.Sign(other.y);
        }

        public bool IsPerpendicular(Vector2D other)
        {
            float delta = Maths.Max(1, x + y + other.x + other.y) * Maths.Epsilon;
            return Maths.Equals(Dot(this, other), 0, delta);
        }

        public Vector2D Normalized()
        {
            float sum = x * x + y * y;

            if (sum == 0)
            {
                return Zero;
            }
            else
            {
                float factor = 1 / Maths.Sqrt(sum);
                return new Vector2D(x * factor, y * factor);
            }
        }

        public Vector2D Truncated(float maxLength)
        {
            Contract.Requires(maxLength.IsRational());
            Contract.Requires(maxLength >= 0);

            if (Length > maxLength)
            {
                return Normalized() * maxLength;
            }
            else
            {
                return new Vector2D(x, y);
            }
        }

        public Vector2D Perpendicular()
        {
            return new Vector2D(-y, x);
        }

        public Vector2D Rotated(float radians)
        {
            float m11 = Maths.Cos(radians);
            float m12 = Maths.Sin(radians);
            float m21 = -m12;
            float m22 = m11;

            return new Vector2D(x * m11 + y * m21, x * m12 + y * m22);
        }
    }
}
